package com.zzh.partnersys.im.sensitiveword.algorithm;

import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

/**
 * ���дʹ�����
 *
 * @author zhaoyuhang
 * @date 2023/06/19
 */
public final class DFAFilter implements SensitiveWordFilter {

    private DFAFilter() {
    }

    private static Word root = new Word(' '); // ���д��ֵ�ĸ���??
    private final static char replace = '*'; // ����ַ�
    private final static String skipChars = " !*-+_=,��.@;:����������?����()����[]����<>����\"����"; // ������Щ�ַ��ͻ�����
    private final static Set<Character> skipSet = new HashSet<>(); // ������Щ�ַ��ͻ�����

    static {
        for (char c : skipChars.toCharArray()) {
            skipSet.add(c);
        }
    }

    public static DFAFilter getInstance() {
        return new DFAFilter();
    }


    /**
     * �ж��ı����Ƿ�������д�
     *
     * @param text �ı�
     * @return true: ��������?? false: ���������д�
     */
    public boolean hasSensitiveWord(String text) {
        if (StringUtils.isBlank(text)) return false;
        return !Objects.equals(filter(text), text);
    }

    /**
     * ���д���??
     *
     * @param text ���滻��??
     * @return �滻����ı�
     */
    public String filter(String text) {
        StringBuilder result = new StringBuilder(text);
        int index = 0;
        while (index < result.length()) {
            char c = result.charAt(index);
            if (skip(c)) {
                index++;
                continue;
            }
            Word word = root;
            int start = index;
            boolean found = false;
            for (int i = index; i < result.length(); i++) {
                c = result.charAt(i);
                if (skip(c)) {
                    continue;
                }
                if (c >= 'A' && c <= 'Z') {
                    c += 32;
                }
                word = word.next.get(c);
                if (word == null) {
                    break;
                }
                if (word.end) {
                    found = true;
                    for (int j = start; j <= i; j++) {
                        result.setCharAt(j, replace);
                    }
                    index = i;
                }
            }
            if (!found) {
                index++;
            }
        }
        return result.toString();
    }


    /**
     * �������д���??
     *
     * @param words ���д���??
     */
    public void loadWord(List<String> words) {
        if (!CollectionUtils.isEmpty(words)) {
            Word newRoot = new Word(' ');
            words.forEach(word -> loadWord(word, newRoot));
            root = newRoot;
        }
    }

    /**
     * ��������??
     *
     * @param word ??
     */
    public void loadWord(String word, Word root) {
        if (StringUtils.isBlank(word)) {
            return;
        }
        Word current = root;
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            // ����Ǵ�д��?? ת��ΪС??
            if (c >= 'A' && c <= 'Z') {
                c += 32;
            }
            if (skip(c)) {
                continue;
            }
            Word next = current.next.get(c);
            if (next == null) {
                next = new Word(c);
                current.next.put(c, next);
            }
            current = next;
        }
        current.end = true;
    }


    /**
     * ���ı��ļ��м������д���??
     *
     * @param path �ı��ļ��ľ���·??
     */
    public void loadWordFromFile(String path) {
        try (InputStream inputStream = Files.newInputStream(Paths.get(path))) {
            loadWord(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * �����м������д��б�
     *
     * @param inputStream �ı��ļ�����??
     * @throws IOException IO�쳣
     */
    public void loadWord(InputStream inputStream) throws IOException {
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
            String line;
            ArrayList<String> list = new ArrayList<>();
            while ((line = reader.readLine()) != null) {
                list.add(line);
            }
            loadWord(list);
        }
    }

    /**
     * �ж��Ƿ���Ҫ������ǰ��??
     *
     * @param c �������??
     * @return true: ��Ҫ��?? false: ����Ҫ��??
     */
    private boolean skip(char c) {
        return skipSet.contains(c);
    }

    /**
     * ���д���
     */
    private static class Word {
        // ��ǰ�ַ�
        private final char c;

        // ������ʶ
        private boolean end;

        // ��һ�㼶�����д��ֵ�
        private Map<Character, Word> next;

        public Word(char c) {
            this.c = c;
            this.next = new HashMap<>();
        }
    }
}
